﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace Birkelund.QueryStrings.Internals
{
    /// <summary>
    /// This class is responsible for reading string-only from the querystring.
    /// </summary>
    internal class QueryStringReader
    {
        public ICollection<TokenKeyValue> Tokens { get; private set; }

        #region Constructors
        private QueryStringReader(Uri uri, bool checkForDecryption)
        {
            ArgumentValidation.ThrowIfNull(uri, "uri");

            if (QueryStringConfiguration.Instance.Encryptor == null || !checkForDecryption)
            {
                Tokens = HttpUtility.ParseQueryString(uri.Query)
                    .ToTokenKeyValues()
                    .ToList();
            }
            else
            {
                QueryStringReader innerReader = new QueryStringReader(uri, false);
                ITokenKey fullEncryptionKey = FullEncryptionKeyToken.Instance;

                if (!innerReader.Exists(fullEncryptionKey))
                {
                    FailureArgs args = new FailureArgs(null, null, uri, FailureReason.FullEncryptionTokenDoesNotExist, null);
                    throw new TokenParserException(args);
                }
                else if (innerReader.Tokens.Count != 1)
                {
                    //it is required that there is only one token when using full querystring encryption
                    FailureArgs args = new FailureArgs(null, null, uri, FailureReason.FullEncryptionTokenIsNotTheOnlyToken, null);
                    throw new TokenParserException(args);
                }
                
                try
                {
                    string encryptedValue = innerReader.ReadStringValue(fullEncryptionKey, UriEncodingMode.HandleEncoding);
                    string clearText = QueryStringConfiguration.Instance.Encryptor.Decrypt(encryptedValue);
                    Tokens = HttpUtility.ParseQueryString(clearText)
                        .ToTokenKeyValues()
                        .ToList();
                }
                catch (Exception ex)
                {
                    FailureArgs args = new FailureArgs(null, null, uri, FailureReason.FullEncryptionError, null, ex);
                    throw new TokenParserException(args);
                }
            }
        }

        private QueryStringReader(Uri uri)
            : this(uri, true)
        {
        }
        #endregion

        #region Class Initializers
        public static QueryStringReader CreateFromCurrentUrl()
        {
            return CreateFromUrl(Common.CurrentUrl);
        }

        public static QueryStringReader CreateFromUrl(Uri url)
        {
            return new QueryStringReader(url);
        } 
        #endregion

        #region Methods
        public bool Exists(ITokenKey key)
        {
            ArgumentValidation.ThrowIfNull(key, "key");

            int count = Tokens.Count(x => Common.AreEqual(x.Key, key.KeyName));
            return count > 0;
        }

        public string ReadStringValue(ITokenKey key, UriEncodingMode urlEncodingMode)
        {
            ArgumentValidation.ThrowIfNull(key, "key");

            if (Exists(key))
            {
                string tokenValue = Tokens
                    .Single(x => Common.AreEqual(x.Key, key.KeyName))
                    .Value;

                if (urlEncodingMode == UriEncodingMode.HandleEncoding && tokenValue != null)
                {
                    return HttpUtility.UrlDecode(tokenValue);
                }
                else
                {
                    return tokenValue;
                }
            }
            return null;
        }
        #endregion
    }
}